function makeTransformFirstChar(fn) {
  return function transformStr(s) {
    if (typeof s !== 'string') return s;
    const head = fn(s.charAt(0));
    const tail = s.slice(1);
    return `${head}${tail}`;
  };
}

const upperCaseFirstChar = makeTransformFirstChar(c => c.toUpperCase());
const lowerCaseFirstChar = makeTransformFirstChar(c => c.toLowerCase());

function makeCharToUpperReplacer(replaceStr) {
  const replaceStringList = Array.isArray(replaceStr)
    ? replaceStr
    : [replaceStr];
  const regStr = replaceStringList.map(c => `([${c}]+[^${c}])`).join('|');
  const reg = new RegExp(regStr, 'g');

  function charToUpper(string) {
    if (typeof string !== 'string' || !string) return string;
    // eslint-disable-next-line
    return string.replace(reg, (_, ...rest) => {
      for (let i = 0; i < rest.length; i += 1) {
        const c = rest[i];
        if (c !== undefined) {
          const replaceChar = replaceStringList[i];
          return charToUpper(upperCaseFirstChar(c.replace(replaceChar, '')));
        }
      }
    });
  }

  return charToUpper;
}

const upperCaseUnderSlash = makeCharToUpperReplacer(['_', '/']);

function upperCaseUnderSlashLearChar(string) {
  return upperCaseFirstChar(upperCaseUnderSlash(string));
}

function lowerCaseUnderSlashLearChar(s) {
  return lowerCaseFirstChar(upperCaseUnderSlash(s));
}

function pick(setting) {
  const isObject = some => typeof some === 'object' && some !== null;
  const isArray = some => some instanceof Array;
  const isString = some => typeof some === 'string';

  function typeConvert(value, typeString, ...typeParams) {
    if (!typeString) return value;
    switch (typeString.trim().toLowerCase()) {
      case 'float':
        return parseFloat(value, ...typeParams);
      case 'int':
        return parseInt(value, ...typeParams);
      case 'array':
        return Array.isArray(value) ? value : [];
      default:
        return value;
    }
  }

  function deepProp(object, string) {
    const [propKey, propType, ...propTypeParams] = string.split(',');
    const props = propKey.trim().split('.');
    const value = props.reduce(
      (o, key) => (o && isObject(o) ? o[key] : o),
      object
    );
    return {
      key: props[props.length - 1],
      value: typeConvert(value, propType, ...propTypeParams)
    };
  }

  function pickArray(array) {
    return function pickArrayFunc(object) {
      return array.reduce((o, k) => {
        const { key, value } = deepProp(object, k);
        return { ...o, [key]: value };
      }, {});
    };
  }

  function pickObject(objectSetting) {
    return function pickObjectFunc(object) {
      return Object.keys(objectSetting).reduce((o, k) => {
        const { value } = deepProp(object, objectSetting[k]);
        return { ...o, [k]: value };
      }, {});
    };
  }

  if (isArray(setting)) {
    return pickArray(setting);
  }
  if (isObject(setting)) {
    return pickObject(setting);
  }
  if (isString(setting)) {
    return obj => deepProp(obj, setting).value;
  }

  throw new Error('参数必须为String、Object、Array类型');
}

async function getMediaDuration(src, type) {
  let $media = null;
  switch (type.toUpperCase()) {
    case 'AUDIO':
      $media = document.createElement('audio');
      break;
    case 'VIDEO':
      $media = document.createElement('video');
      break;
    default:
      throw new Error('no such media type');
  }

  const duration = await new Promise(resolve => {
    const events = ['loadedmetadata'];

    function removeListeners() {
      events.forEach(evt => {
        // eslint-disable-next-line
        $media.removeEventListener(evt, updateDuration);
      });
    }

    function updateDuration() {
      resolve($media.duration);
      removeListeners();
    }

    events.forEach(evt => {
      $media.addEventListener(evt, updateDuration);
    });

    const mediaSrc = src instanceof File ? URL.createObjectURL(src) : src;
    $media.src = mediaSrc;
  });

  return duration;
}

function getRandomStream() {
  const s = Math.random()
    .toString(16)
    .slice(2)
    .toUpperCase();
  let i = 0;
  const next = () => {
    i += 1;
    return i < s.length ? { value: s.charAt(i), next } : getRandomStream();
  };
  return { value: s.charAt(i), next };
}

function noLeadingNumber(c, pos) {
  return !(pos === 0 && /[0-9]/.test(c));
}

function getRandomId(len, filter = noLeadingNumber) {
  const getChars = (pos, stream, s) => {
    if (pos >= len) return s;
    return filter(stream.value, pos)
      ? getChars(pos + 1, stream.next(), s + stream.value)
      : getChars(pos, stream.next(), s);
  };
  return getChars(0, getRandomStream(), '');
}

const hasProps = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
const isArray = s => Array.isArray(s);
const isPlainObject = s => s && typeof s === 'object';
const isString = s => typeof s === 'string';
const isEnumerable = s => isArray(s) || isPlainObject(s);

function excludeKey(o, k) {
  if (isArray(o)) {
    return o.filter((t, i) => i !== k);
  }
  if (isPlainObject(o)) {
    return Object.keys(o)
      .filter(s => s !== k)
      .reduce((r, m) => ({ ...r, [m]: o[m] }), {});
  }
  return o;
}

function excludeKeyList(o, kl) {
  if (!isEnumerable(o) || !kl.length) return o;
  const k = kl.shift();
  if (!hasProps(o, k)) return o;
  if (kl.length === 0) {
    return excludeKey(o, k);
  }
  if (isArray(o)) {
    return o.map((t, i) => (i === k ? excludeKeyList(t, kl) : t));
  }
  if (isPlainObject(o)) {
    return { ...o, [k]: excludeKeyList(o[k], kl) };
  }
  return o;
}

function excludeKeysList(data, keys) {
  return keys.reduce((o, k) => excludeKeyList(o, k.split('.')), data);
}

function excludeKeys(data, keys) {
  if (isString(keys)) return excludeKeysList(data, [keys]);
  if (isArray(keys)) return excludeKeysList(data, keys);
  throw new Error(`keys must be typeof Array or string, got ${typeof keys}`);
}

function onlyNumber(val) {
  return `${val}`
    .split('')
    .filter(t => /[0-9]/.test(t))
    .join('');
}

export {
  pick,
  getMediaDuration,
  upperCaseFirstChar,
  upperCaseUnderSlashLearChar,
  lowerCaseUnderSlashLearChar,
  getRandomId,
  getRandomStream,
  excludeKeys,
  onlyNumber
};
